package v1alpha1

import (
	apps "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
)

const (
	defaultHostNetwork = false
)

// ComponentAccessor is the interface to access component details, which respects the cluster-level properties
// and component-level overrides
type ComponentAccessor interface {
	MemberType() MemberType
	AdditionalContainers() []corev1.Container

	BuildPodSpec() corev1.PodSpec
	Env() []corev1.EnvVar
	EnvFrom() []corev1.EnvFromSource
	DNSConfig() *corev1.PodDNSConfig
	DnsPolicy() corev1.DNSPolicy
	HostNetwork() bool
	ImagePullPolicy() corev1.PullPolicy
	ImagePullSecrets() []corev1.LocalObjectReference

	NodeSelector() map[string]string
	SchedulerName() string

	/*


		Affinity() *corev1.Affinity
		PriorityClassName() *string

		Labels() map[string]string
		Annotations() map[string]string
		Tolerations() []corev1.Toleration
		PodSecurityContext() *corev1.PodSecurityContext

		ConfigUpdateStrategy() ConfigUpdateStrategy


		AdditionalContainers() []corev1.Container
		InitContainers() []corev1.Container
		AdditionalVolumes() []corev1.Volume
		AdditionalVolumeMounts() []corev1.VolumeMount
		TerminationGracePeriodSeconds() *int64
		StatefulSetUpdateStrategy() apps.StatefulSetUpdateStrategyType
		PodManagementPolicy() apps.PodManagementPolicyType
		TopologySpreadConstraints() []corev1.TopologySpreadConstraint
	*/
	// SuspendAction() *SuspendAction
}

type realComponentAccessor struct {
	component MemberType
	name      string
	kind      string

	imagePullPolicy           corev1.PullPolicy
	imagePullSecrets          []corev1.LocalObjectReference
	hostNetwork               *bool
	affinity                  *corev1.Affinity
	priorityClassName         *string
	schedulerName             string
	clusterNodeSelector       map[string]string
	clusterAnnotations        map[string]string
	clusterLabels             map[string]string
	tolerations               []corev1.Toleration
	dnsConfig                 *corev1.PodDNSConfig
	dnsPolicy                 corev1.DNSPolicy
	configUpdateStrategy      ConfigUpdateStrategy
	statefulSetUpdateStrategy apps.StatefulSetUpdateStrategyType
	podManagementPolicy       apps.PodManagementPolicyType
	podSecurityContext        *corev1.PodSecurityContext
	//topologySpreadConstraints []TopologySpreadConstraint
	//suspendAction             *SuspendAction

	// ComponentSpec is the Component Spec
	ComponentSpec *ComponentSpec
}

func (a *realComponentAccessor) MemberType() MemberType {
	return a.component
}

func (a *realComponentAccessor) AdditionalContainers() []corev1.Container {
	if a.ComponentSpec == nil {
		return nil
	}
	return a.ComponentSpec.AdditionalContainers
}

func (a *realComponentAccessor) BuildPodSpec() corev1.PodSpec {
	spec := corev1.PodSpec{
		SchedulerName:   a.SchedulerName(),
		Affinity:        a.Affinity(),
		NodeSelector:    a.NodeSelector(),
		HostNetwork:     a.HostNetwork(),
		RestartPolicy:   corev1.RestartPolicyAlways,
		Tolerations:     a.Tolerations(),
		SecurityContext: a.PodSecurityContext(),
		DNSPolicy:       a.DnsPolicy(),
		DNSConfig:       a.DNSConfig(),

		//TopologySpreadConstraints: a.TopologySpreadConstraints(),
	}
	if a.PriorityClassName() != nil {
		spec.PriorityClassName = *a.PriorityClassName()
	}
	if a.ImagePullSecrets() != nil {
		spec.ImagePullSecrets = a.ImagePullSecrets()
	}
	/*
		if a.TerminationGracePeriodSeconds() != nil {
			spec.TerminationGracePeriodSeconds = a.TerminationGracePeriodSeconds()
		}
	*/
	return spec
}

func (a *realComponentAccessor) Affinity() *corev1.Affinity {
	if a.ComponentSpec == nil || a.ComponentSpec.Affinity == nil {
		return a.affinity
	}
	return a.ComponentSpec.Affinity
}

func (a *realComponentAccessor) DNSConfig() *corev1.PodDNSConfig {
	if a.ComponentSpec == nil || a.ComponentSpec.DNSConfig == nil {
		return a.dnsConfig
	}
	return a.ComponentSpec.DNSConfig
}

func (a *realComponentAccessor) DnsPolicy() corev1.DNSPolicy {
	if a.ComponentSpec != nil && a.ComponentSpec.DNSPolicy != "" {
		return a.ComponentSpec.DNSPolicy
	}

	if a.dnsPolicy != "" {
		return a.dnsPolicy
	}

	if a.HostNetwork() {
		return corev1.DNSClusterFirstWithHostNet
	}
	return corev1.DNSClusterFirst // same as kubernetes default
}
func (a *realComponentAccessor) Env() []corev1.EnvVar {
	if a.ComponentSpec == nil {
		return nil
	}
	return a.ComponentSpec.Env
}

func (a *realComponentAccessor) EnvFrom() []corev1.EnvFromSource {
	if a.ComponentSpec == nil {
		return nil
	}
	return a.ComponentSpec.EnvFrom
}

func (a *realComponentAccessor) HostNetwork() bool {
	if a.ComponentSpec == nil || a.ComponentSpec.HostNetwork == nil {
		if a.hostNetwork == nil {
			return defaultHostNetwork
		}
		return *a.hostNetwork
	}
	return *a.ComponentSpec.HostNetwork
}

func (a *realComponentAccessor) ImagePullPolicy() corev1.PullPolicy {
	if a.ComponentSpec == nil || a.ComponentSpec.ImagePullPolicy == nil {
		return a.imagePullPolicy
	}
	return *a.ComponentSpec.ImagePullPolicy
}

func (a *realComponentAccessor) ImagePullSecrets() []corev1.LocalObjectReference {
	if a.ComponentSpec == nil || a.ComponentSpec.ImagePullSecrets == nil {
		return a.imagePullSecrets
	}
	return a.ComponentSpec.ImagePullSecrets
}

func (a *realComponentAccessor) NodeSelector() map[string]string {
	sel := map[string]string{}
	for k, v := range a.clusterNodeSelector {
		sel[k] = v
	}
	if a.ComponentSpec != nil {
		for k, v := range a.ComponentSpec.NodeSelector {
			sel[k] = v
		}
	}
	return sel
}

func (a *realComponentAccessor) PodSecurityContext() *corev1.PodSecurityContext {
	if a.ComponentSpec == nil || a.ComponentSpec.PodSecurityContext == nil {
		return a.podSecurityContext
	}
	return a.ComponentSpec.PodSecurityContext
}

func (a *realComponentAccessor) PriorityClassName() *string {
	if a.ComponentSpec == nil || a.ComponentSpec.Affinity == nil {
		return a.priorityClassName
	}
	return a.ComponentSpec.PriorityClassName
}

func (a *realComponentAccessor) SchedulerName() string {
	if a.ComponentSpec == nil || a.ComponentSpec.SchedulerName == nil {
		return a.schedulerName
	}
	return *a.ComponentSpec.SchedulerName
}

func (a *realComponentAccessor) Tolerations() []corev1.Toleration {
	if a.ComponentSpec == nil || len(a.ComponentSpec.Tolerations) == 0 {
		return a.tolerations
	}
	return a.ComponentSpec.Tolerations
}

func buildPostgresClusterComponentAccessor(c MemberType, pg *PostgresCluster, componentSpec *ComponentSpec) ComponentAccessor {
	spec := &pg.Spec
	return &realComponentAccessor{
		name:                      pg.Name,
		kind:                      PostgresClusterKind,
		component:                 c,
		imagePullPolicy:           spec.ImagePullPolicy,
		imagePullSecrets:          spec.ImagePullSecrets,
		hostNetwork:               spec.HostNetwork,
		affinity:                  spec.Affinity,
		priorityClassName:         spec.PriorityClassName,
		schedulerName:             spec.SchedulerName,
		clusterNodeSelector:       spec.NodeSelector,
		clusterLabels:             spec.Labels,
		clusterAnnotations:        spec.Annotations,
		tolerations:               spec.Tolerations,
		dnsConfig:                 spec.DNSConfig,
		dnsPolicy:                 spec.DNSPolicy,
		configUpdateStrategy:      spec.ConfigUpdateStrategy,
		statefulSetUpdateStrategy: spec.StatefulSetUpdateStrategy,
		podManagementPolicy:       spec.PodManagementPolicy,
		podSecurityContext:        spec.PodSecurityContext,
		//topologySpreadConstraints: spec.TopologySpreadConstraints,
		//suspendAction:             spec.SuspendAction,

		ComponentSpec: componentSpec,
	}
}

// BasePartroniSpec returns the base spec of patroni and postgres servers
func (pg *PostgresCluster) BasePartroniSpec() ComponentAccessor {
	var spec *ComponentSpec
	if pg.Spec.Postgres != nil {
		spec = &pg.Spec.Postgres.ComponentSpec
	}

	return buildPostgresClusterComponentAccessor(PatroniMemberType, pg, spec)
}
